﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using SuperDarts.Gameplay.Views;
using SuperDarts.ScreenManagement;
using SuperDarts.Screens;
using SuperDarts.Screens.GameScreens;

namespace SuperDarts.Gameplay.Modes
{
    public enum Direction
    {
        Asc,
        Desc
    }

    /// <summary>
    ///     This class serves as the base for all GameModes
    /// </summary>
    public abstract class GameMode
    {
        /// <summary>
        ///     This method is run at the beginning of a new round
        /// </summary>
        private void startRound()
        {
            //Reset current player index and increment round
            CurrentPlayerIndex = 0;
            CurrentRoundIndex++;

            //Check if its the last round
            if (CurrentRoundIndex == MaxRounds - 1)
            {
                // Play last round sound
                SuperDarts.SoundManager.PlaySound(SoundCue.LastRound);
            }
            else
            {
                // Play new round sound
                SuperDarts.SoundManager.PlaySound(SoundCue.NewRound);
            }

            showNewRoundMessageScreen();
        }

        /// <summary>
        ///     This method is called by pressing the skip button, for example when a player missed a dart
        /// </summary>
        public virtual void ForcePlayerChange()
        {
            if (!IsGameOver)
            {
                for (; CurrentPlayerRound.Darts.Count < DartsPerRound;)
                {
                    RegisterDart(0, 0);
                }
            }
        }

        /// <summary>
        ///     This method is triggered when the player change message has timed out or has been closed
        /// </summary>
        private void playerChange()
        {
            //Stop any award videos that are currently playing
            _awardScreen.Stop();

            //Increment player index
            CurrentPlayerIndex++;

            //Check if it's a new round
            if (CurrentPlayerIndex > SuperDarts.Players.Count - 1)
            {
                startRound();
            }
            else
            {
                SuperDarts.SoundManager.PlaySound(SoundCue.ThrowStart);
                showThrowDartsMessage();
            }
        }

        /// <summary>
        ///     This method is called at the end of a round (before the start of a new round)
        /// </summary>
        /// <param name="isGameOver"></param>
        public virtual void OnNextPlayer(out bool isGameOver)
        {
            isGameOver = false;
        }

        /// <summary>
        ///     This method is called when the round is over and we should switch players
        /// </summary>
        public void NextPlayer()
        {
            bool isGameOver;
            OnNextPlayer(out isGameOver);

            //Check if it was the last player and the last round
            var lastPlayerAndRound = (CurrentPlayerIndex == SuperDarts.Players.Count - 1 && CurrentRoundIndex == MaxRounds - 1);
            if (isGameOver || lastPlayerAndRound)
            {
                GameOver();
            }
            else
            {
                ShowPlayerChangeScreen();

                if (SuperDarts.Options.PlayAwards)
                {
                    PlayAwards();
                }
            }
        }

        /// <summary>
        ///     Checks if any conditions for the awards are met and plays the award video
        /// </summary>
        public virtual void PlayAwards()
        {
            if (CurrentPlayerRound.Darts.Count != 3)
            {
                return;
            }

            if (GetScore(CurrentPlayerRound) == 180)
            {
                _awardScreen.Play(AwardCue.TonEighty);
            }
            else if (
                CurrentPlayerRound.Darts.TrueForAll(
                    delegate(Dart dart) { return dart.Segment == 25 && dart.Multiplier == 2; }))
            {
                //Play award three in the black
                _awardScreen.Play(AwardCue.ThreeInTheBlack);
            }
            else if (CurrentPlayerRound.Darts.TrueForAll(delegate(Dart dart) { return dart.Segment == 25; }))
            {
                //Play award hattrick!
                _awardScreen.Play(AwardCue.HatTrick);
            }
            else if (GetScore(CurrentPlayerRound) > 150)
            {
                _awardScreen.Play(AwardCue.HighTon);
            }
            else if (GetScore(CurrentPlayerRound) >= 100)
            {
                //Play award low ton!
                _awardScreen.Play(AwardCue.LowTon);
            }
            else if (CurrentPlayerRound.Darts.All(x =>
                x.Segment != 0 &&
                x.Multiplier != 1 &&
                x.Segment == CurrentPlayerRound.Darts[0].Segment &&
                x.Multiplier == CurrentPlayerRound.Darts[0].Multiplier))
            {
                _awardScreen.Play(AwardCue.ThreeInABed);
            }
        }

        /// <summary>
        ///     This method is fired by the serial manager when a dart hits the dartboard
        /// </summary>
        /// <param name="segment"></param>
        /// <param name="multiplier"></param>
        public void RegisterDart(int segment, int multiplier)
        {
            if (IsGameOver || Paused)
            {
                return;
            }

            // Check if we have already thrown all darts for this round
            if (CurrentPlayerRound.Darts.Count >= DartsPerRound)
            {
                return;
            }

            //Else register the dart

            //Exit screens in case they are still in view
            _newRoundTimeoutScreen.RemoveScreen();
            _throwDartsScreen.RemoveScreen();

            //Add the dart and play sound
            var dart = new Dart(CurrentPlayer, segment, multiplier);
            CurrentPlayerRound.Darts.Add(dart);
            playSound(dart);

            //Run this method to allow the game modes to run their logic whenever a dart is registered
            OnDartHit(dart);
        }

        /// <summary>
        ///     This method is called just as a dart hits the board
        /// </summary>
        /// <param name="dart"></param>
        public virtual void OnDartHit(Dart dart)
        {
            var lastThrow = CurrentPlayerRound.Darts.Count == DartsPerRound;
            //Check if its the last throw for the currentplayer
            if (lastThrow)
            {
                NextPlayer();
            }
        }

        public abstract int GetScore(Player player);

        public virtual int GetScore(Round r)
        {
            return r.Darts.Sum(x => GetScore(x));
        }

        public virtual int GetScore(Dart d)
        {
            if (d.Segment == 25)
            {
                return d.Segment*2; // Regardless of multiplier (single/double bull)
            }

            return d.Segment*d.Multiplier;
        }

        #region LoadContent

        public virtual void LoadContent(ContentManager content)
        {
            BaseScreen.LoadContent(content);
            _awardScreen.LoadContent(content);
            _playerChangeScreen.LoadContent(content);
        }

        #endregion

        public virtual void GameOver()
        {
            if (IsGameOver)
            {
                return;
            }

            IsGameOver = true;
            SuperDarts.SoundManager.PlaySound(SoundCue.Won);

            var leaders = GetLeaders();

            var text = "Winner";

            if (leaders.Count > 1)
                text = "Draw";

            leaders.ForEach(p => text += " " + p.Name);

            var gameOverScreen = new MessageBoxScreen("Game Over", text, MessageBoxButtons.Ok);
            gameOverScreen.OnOk += delegate { GameplayScreen.Pause(); };
            SuperDarts.ScreenManager.AddScreen(gameOverScreen);
        }

        public virtual List<Player> GetLeaders()
        {
            if (ScoringDirection == Direction.Asc)
                return SuperDarts.Players.GroupBy(p => GetScore(p)).OrderBy(g => g.Key).First().ToList();
            return SuperDarts.Players.GroupBy(p => GetScore(p)).OrderBy(g => g.Key).Last().ToList();
        }

        private void removeDart()
        {
            if (CurrentPlayerRound.Darts.Count > 0)
            {
                CurrentPlayerRound.Darts.RemoveAt(CurrentPlayerRound.Darts.Count - 1);
                IsGameOver = false;
            }
        }

        public virtual void Unthrow()
        {
            //Remove last dart of the current players throws
            if (CurrentPlayerRound.Darts.Count > 0)
            {
                removeDart();
            }
            //Back up one player and remove dart
            else if (CurrentPlayerIndex > 0)
            {
                CurrentPlayerIndex--;
                removeDart();
            }
            //Back up one round
            else if (CurrentRoundIndex > 0)
            {
                CurrentRoundIndex--;
                CurrentPlayerIndex = SuperDarts.Players.Count - 1;
                removeDart();
            }
        }

        #region SoundHelper

        private static void playSound(Dart dart)
        {
            if (dart.Segment == 25)
            {
                if (dart.Multiplier == 2)
                    SuperDarts.SoundManager.PlaySound(SoundCue.DoubleBull);
                else
                    SuperDarts.SoundManager.PlaySound(SoundCue.SingleBull);
            }
            else
            {
                if (dart.Multiplier == 2)
                    SuperDarts.SoundManager.PlaySound(SoundCue.Double);
                else if (dart.Multiplier == 3)
                    SuperDarts.SoundManager.PlaySound(SoundCue.Triple);
                else
                    SuperDarts.SoundManager.PlaySound(SoundCue.Single);
            }
        }

        #endregion

        #region Fields and Properties

        public int CurrentPlayerIndex;
        public int CurrentRoundIndex;
        public int DartsPerRound = 3;
        public int MaxRounds = 8;

        public bool IsGameOver;
        public bool Paused = false;

        public abstract string Name { get; }

        public BaseView BaseScreen;

        public List<Player> Players
        {
            get { return SuperDarts.Players; }
        }

        public Round CurrentPlayerRound
        {
            get { return CurrentPlayer.Rounds[CurrentRoundIndex]; }
        }

        public Player CurrentPlayer
        {
            get { return SuperDarts.Players[CurrentPlayerIndex]; }
        }

        private readonly AwardScreen _awardScreen;

        public GameplayScreen GameplayScreen;
        private readonly PlayerChangeScreen _playerChangeScreen;
        private readonly TimeoutScreen _throwDartsScreen;
        private readonly TimeoutScreen _newRoundTimeoutScreen;
        public Direction ScoringDirection = Direction.Desc;

        #endregion

        #region Constructor

        public GameMode(int players)
        {
            SuperDarts.Players.Clear();

            addPlayers(players);

            _awardScreen = new AwardScreen();

            _playerChangeScreen = new PlayerChangeScreen("Player Change",
                TimeSpan.FromSeconds(SuperDarts.Options.PlayerChangeTimeout));
            _playerChangeScreen.OnTimeout = playerChange;
            _throwDartsScreen = new TimeoutScreen(CurrentPlayer.Name + " throw darts!", TimeSpan.FromSeconds(3));

            _newRoundTimeoutScreen = new TimeoutScreen("Round " + (CurrentRoundIndex + 1), TimeSpan.FromSeconds(3));
            _newRoundTimeoutScreen.OnTimeout = showThrowDartsMessage;
        }

        private void addPlayers(int players)
        {
            for (var i = 0; i < players; i++)
            {
                var p = new Player("Player " + (i + 1));
                SuperDarts.Players.Add(p);
                for (var j = 0; j < MaxRounds; j++)
                {
                    p.Rounds.Add(new Round());
                }
            }
        }

        #endregion

        #region Show Message Screen Helpers

        public void ShowPlayerChangeScreen()
        {
            _playerChangeScreen.Timeout = TimeSpan.FromSeconds(SuperDarts.Options.PlayerChangeTimeout);
            SuperDarts.ScreenManager.AddScreen(_playerChangeScreen);
        }

        private void showNewRoundMessageScreen()
        {
            _newRoundTimeoutScreen.Text = "Round " + (CurrentRoundIndex + 1);
            _newRoundTimeoutScreen.ElapsedTime = 0;
            SuperDarts.ScreenManager.AddScreen(_newRoundTimeoutScreen);
        }

        private void showThrowDartsMessage()
        {
            _throwDartsScreen.Text = CurrentPlayer.Name + " throw darts!";
            _throwDartsScreen.ElapsedTime = 0;
            SuperDarts.ScreenManager.AddScreen(_throwDartsScreen);
        }

        #endregion

        #region Update and Draw

        public virtual void Update(GameTime gameTime, bool isCoveredByOtherScreen)
        {
            BaseScreen.Update(gameTime, isCoveredByOtherScreen);
        }

        public virtual void Draw(SpriteBatch spriteBatch)
        {
            BaseScreen.Draw(spriteBatch);
            _awardScreen.Draw(spriteBatch);
        }

        #endregion
    }
}